/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "commonheaders.h"
#include "classExImContactBase.h"

namespace NExImport {

static HANDLE CListFindGroup(LPCSTR pszGroup)
{
	CHAR buf[4];
	BYTE i;
	DBVARIANT dbv;

	for(i = 0; i < 255; i++) {
		_itoa(i, buf, 10);
		if(DBGetSettingUtf8(NULL, "CListGroups", buf, &dbv))
			break;
		if(dbv.pszVal && pszGroup && !_stricmp(dbv.pszVal + 1, pszGroup)) {
			DBFreeVariant(&dbv);
			return (HANDLE)(i+1);
		}
		DBFreeVariant(&dbv);
	}
	return INVALID_HANDLE_VALUE;
}

/**
 * name:	CExImContactBase
 * class:	CExImContactBase
 * desc:	default constructor
 * param:	none
 * return:	nothing
 **/
CExImContactBase::CExImContactBase()
{
	_isNewContact = FALSE;
	_hContact = INVALID_HANDLE_VALUE;
	ZeroMemory(&_dbvUID, sizeof(DBVARIANT));
}

/**
 * name:	~CExImContactBase
 * class:	CExImContactBase
 * desc:	default denstructor
 * param:	none
 * return:	nothing
 **/
CExImContactBase::~CExImContactBase()
{
	DBFreeVariant(&_dbvUID);
}

/**
 * name:	fromDB
 * class:	CExImContactBase
 * desc:	get contact information from database
 * param:	hContact	- handle to contact whose information to read
 * return:	TRUE if successful or FALSE otherwise
 **/
BOOLEAN CExImContactBase::fromDB(HANDLE hContact)
{
	BOOLEAN ret = FALSE;
	LPCSTR pszBaseProto;
	DBVARIANT dbv;

	_hContact = hContact;

	_strProto = "";
	_strNick = "";
	_strDisp = "";
	_strGroup = "";
	_strUIDKey = "";
	DBFreeVariant(&_dbvUID);
	if(!_hContact) return TRUE;

	
	pszBaseProto = DBGetContactBaseProto(_hContact);

	// MS_PROTO_GETCONTACTBASEPROTO fails because the protocol is no longer installed 
	if(pszBaseProto == NULL || (INT)pszBaseProto == CALLSERVICE_NOTFOUND) {
		CHAR proto[128];

		// try to get raw protocol name
		if(!DBGetSettingStaticString(_hContact, "Protocol", "p", proto, sizeof(proto))) {
			_strProto = proto;

			// nickname
			if(!DBGetSettingUtf8(_hContact, pszBaseProto, SET_CONTACT_NICK, &dbv)) {
				_strNick = dbv.pszVal;
				DBFreeVariant(&dbv);
				ret = TRUE;
			}
		}
	}
	else {
		LPCSTR uidSetting;

		// protocol
		_strProto = pszBaseProto;

		// nickname
		if(!DBGetSettingUtf8(_hContact, pszBaseProto, SET_CONTACT_NICK, &dbv)) {
			_strNick = dbv.pszVal;
			DBFreeVariant(&dbv);
		}

		// unique id
		uidSetting = (LPCSTR)CallProtoService(pszBaseProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
		if(uidSetting != NULL && (INT)uidSetting != CALLSERVICE_NOTFOUND) {
			_strUIDKey = uidSetting;
			if(!DBGetSettingUtf8(_hContact, pszBaseProto, uidSetting, &_dbvUID)) {
				ret = TRUE;
			}
		}
	}
	if(_hContact && ret) {
		if(!DBGetSettingUtf8(_hContact, "CList", "Group", &dbv)) {
			_strGroup = dbv.pszVal;
			DBFreeVariant(&dbv);
		}
		if(!DBGetSettingUtf8(_hContact, "CList", "MyHandle", &dbv)) {
			_strDisp = dbv.pszVal;
			DBFreeVariant(&dbv);
		}
	}
	return ret;
}

/**
 * name:	fromIni
 * class:	CExImContactBase
 * desc:	get contact information from a row of a ini file
 * param:	row	- the rows data
 * return:	TRUE if successful or FALSE otherwise
 **/
BOOLEAN CExImContactBase::fromIni(string& row)
{
	LPSTR p1, p2;
	LPSTR pszUIDValue, pszUIDSetting, pszProto;
	LPSTR pszBuf = &row[0];
	DWORD cchBuf = row.length();

	// read uid value
	if(cchBuf > 10 && (p1 = mir_strrchr(pszBuf, '*{')) && (p2 = mir_strchr(p1, '}*')) && p1 + 2 < p2) {
		pszUIDValue = p1 + 1;
		*p1 = *(p2 - 1) = 0;

		// insulate the uid setting from buffer pointer
		if(cchBuf > 0 && (p1 = mir_strrchr(pszBuf, '*<')) && (p2 = mir_strchr(p1, '>*')) && p1 + 2 < p2) {
			pszUIDSetting = p1 + 1;
			*p1 = *(p2 - 1) = 0;

			// insulate the protocol name from buffer pointer
			if(cchBuf > 0 && (p1 = mir_strrchr(pszBuf, '*(')) && (p2 = mir_strchr(p1, ')*')) && p1 + 2 < p2) {
				pszProto = p1 + 1;
				*(--p1) = *(p2 - 1) = 0;

				if(strspn(pszUIDValue, "0123456789") == mir_strlen(pszUIDValue)) {
					_dbvUID.dVal = atol(pszUIDValue);
					_dbvUID.type = DBVT_DWORD;
				}
				else {
					_dbvUID.pszVal = mir_strdup(pszUIDValue);
					_dbvUID.type = DBVT_UTF8;
				}
				_strUIDKey = pszUIDSetting;
				_strProto = pszProto;
			}
		}
	}
	// create valid nickname
	_strNick = pszBuf;
	size_t i = _strNick.length();

	while(i > 0 && (_strNick[i] == ' ' || _strNick[i] == '\t')) {
		_strNick[i] = 0;
		i--;
	}
	return FALSE;
}

/**
 * name:	toDB
 * class:	CExImContactBase
 * desc:	searches the database for a contact representing the one
 *			identified by this class or creates a new one if it was not found
 * param:	hMetaContact - a meta contact to add this contact to
 * return:	handle of the contact if successful
 **/
HANDLE CExImContactBase::toDB()
{
	DBCONTACTWRITESETTING cws;
	
	// create new contact if none exists
	if(_hContact == INVALID_HANDLE_VALUE && proto() && uidk() && _dbvUID.type != DBVT_DELETED) {
		// create new contact
		_hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0);
		if(!_hContact) {
			_hContact = INVALID_HANDLE_VALUE;
			return INVALID_HANDLE_VALUE;
		}
		// Add the protocol to the new contact
		if(CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)_hContact, (LPARAM)proto())) {
			CallService(MS_DB_CONTACT_DELETE, (WPARAM)_hContact, 0);
			_hContact = INVALID_HANDLE_VALUE;
			return INVALID_HANDLE_VALUE;
		}
		// write uid to protocol module
		cws.szModule = proto();
		cws.szSetting = uidk();
		cws.value = _dbvUID;
		if(CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)_hContact, (LPARAM)&cws)) {
			CallService(MS_DB_CONTACT_DELETE, (WPARAM)_hContact, 0);
			_hContact = INVALID_HANDLE_VALUE;
			return INVALID_HANDLE_VALUE;
		}
		// write nick and display name
		if(nick()) DBWriteContactSettingStringUtf(_hContact, proto(), SET_CONTACT_NICK, nick());
		if(disp()) DBWriteContactSettingStringUtf(_hContact, MOD_CLIST, "MyHandle", disp());

		// add group
		if(group()) {
			DBWriteContactSettingStringUtf(_hContact, MOD_CLIST, "Group", group());
			if(CListFindGroup(group()) == INVALID_HANDLE_VALUE) {
				WPARAM hGroup = (WPARAM)CallService(MS_CLIST_GROUPCREATE, 0, 0);
				if(hGroup) {
					// renaming twice is stupid but the only way to avoid error dialog telling shit like
					// a group with that name does exist
					CallService(MS_CLIST_GROUPRENAME, hGroup, (LPARAM)group());
					//CallService(MS_CLIST_GROUPRENAME, hGroup, (LPARAM)group());
				}
			}
		}
	}
	return _hContact;
}

/**
 * name:	toIni
 * class:	CExImContactBase
 * desc:	writes the line to an opened ini file which identifies the contact
 *			whose information are stored in this class
 * param:	file	- pointer to the opened file
 * return:	nothing
 **/
VOID CExImContactBase::toIni(FILE* file)
{
	if(_strProto.length() > 0) {
		LPCSTR pszUID = uid2String(FALSE);

		if(_strUIDKey.length() > 0 && pszUID) {
			fprintf(file, "CONTACT: %s *(%s)*<%s>*{%s}*", 
				_strNick.length() > 0 ? _strNick.c_str() : "(UNKNOWN)",
				_strProto.c_str(), _strUIDKey.c_str(), pszUID);
		}
		else {
			fprintf(file, "FROM CONTACT: %s (%s)", 
				_strNick.length() > 0 ? _strNick.c_str() : "(UNKNOWN)",
				_strProto.c_str());
		}
	}
	else {
		fprintf(file, "FROM CONTACT: %s ", _strNick.length() > 0 ? _strNick.c_str() : "(UNKNOWN)");
	}
	fputs("\n\n", file);
}

BOOLEAN CExImContactBase::compareUID(DWORD val, BYTE type)
{
	CHAR buf[32];

	switch(_dbvUID.type) {
		case DBVT_BYTE:
			if(val == _dbvUID.bVal) {
				_dbvUID.type = type;
				return TRUE;
			}
			break;
		case DBVT_WORD:
			if(val == _dbvUID.wVal) {
				_dbvUID.type = type;
				return TRUE;
			}
			break;
		case DBVT_DWORD:
			if(val == _dbvUID.dVal) {
				_dbvUID.type = type;
				return TRUE;
			}
			break;
		case DBVT_UTF8:
			_itoa(val, buf, 10);
			if(_dbvUID.pszVal && !mir_stricmp(buf, _dbvUID.pszVal)) {
				mir_free(_dbvUID.pszVal);
				_dbvUID.type = type;
				return TRUE;
			}
			break;
	}
	return FALSE;
}

LPSTR CExImContactBase::uid2String(BOOLEAN bPrependType)
{
	CHAR szUID[MAX_PATH];
	LPSTR ptr = szUID;

	switch(_dbvUID.type) {
		case DBVT_BYTE:
			if(bPrependType) *ptr++ = 'b';
			_itoa(_dbvUID.bVal, ptr, 10);
			break;
		case DBVT_WORD:
			if(bPrependType) *ptr++ = 'w';
			_itoa(_dbvUID.wVal, ptr, 10);
			break;
		case DBVT_DWORD:
			if(bPrependType) *ptr++ = 'd';
			_itoa(_dbvUID.dVal, ptr, 10);
			break;
		case DBVT_UTF8:
			if(bPrependType) *ptr++ = 'u';
			mir_strncpy(ptr, _dbvUID.pszVal, SIZEOF(szUID)-1);
			break;
		case DBVT_ASCIIZ:
			if(bPrependType) *ptr++ = 'u';
			if(0 > MultiByteToUtf8Static(_dbvUID.pszVal, ptr, SIZEOF(szUID)-1))
				return NULL;
			break;
		case DBVT_WCHAR:
			if(bPrependType) *ptr++ = 'u';
			if(0 > WideCharToUtf8Static(_dbvUID.pwszVal, ptr, SIZEOF(szUID)-1))
				return NULL;
			break;
	}
	return mir_strdup(szUID);
}

BOOLEAN CExImContactBase::isMeta() const
{
	return myGlobals.HaveMetaContacts && !mir_stricmp(proto(), META_PROTO);
}

BOOLEAN CExImContactBase::isHandle(HANDLE hContact)
{
	LPCSTR pszProto;
	DBVARIANT dbv;
	BOOLEAN isEqual = FALSE;

	// owner contact ?
	if(proto() == NULL) return hContact == NULL;

	// compare protocols
	pszProto = DBGetContactBaseProto(hContact);
	if(pszProto == NULL || (INT)pszProto == CALLSERVICE_NOTFOUND || mir_stricmp(pszProto, proto()))
		return FALSE;

	// compare uids
	if(uidk()) {
		// get uid
		if(DBGetSettingUtf8(hContact, pszProto, uidk(), &dbv))
			return FALSE;

		switch(dbv.type) {
		case DBVT_BYTE:
			if(compareUID(dbv.bVal, dbv.type))
				return TRUE;
			break;
		case DBVT_WORD:
			if(compareUID(dbv.wVal, dbv.type))
				return TRUE;
			break;
		case DBVT_DWORD:
			if(compareUID(dbv.dVal, dbv.type))
				return TRUE;
			break;
		case DBVT_UTF8:
			isEqual = (_dbvUID.type == dbv.type && !strcmp(_dbvUID.pszVal, dbv.pszVal));
			break;
		}
		DBFreeVariant(&dbv);
	}
	// compare nicknames
	else if(!DBGetSettingUtf8(hContact, proto(), SET_CONTACT_NICK, &dbv)) {
		if(dbv.type == DBVT_UTF8 && dbv.pszVal && !mir_stricmp(dbv.pszVal, nick())) {
			INT ans = MsgBox(NULL, MB_ICONQUESTION|MB_YESNO, _T("Question"), _T("contact identificaion"),
				_T("The contact %s(%s) has no unique id in the vCard,\nbut there is a contact in your clist with the same nick and protocol.\nDo you wish to use this contact?"),
				nick(), proto());
			isEqual = ans == IDYES;
		}
		DBFreeVariant(&dbv);
	}
	return isEqual;
}

/**
 * name:	handle
 * class:	CExImContactBase
 * desc:	return the handle of the contact
 *			whose information are stored in this class
 * param:	none
 * return:	handle if successful, INVALID_HANDLE_VALUE otherwise
 **/
HANDLE CExImContactBase::findHandle()
{
	HANDLE hContact;

	for(hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, NULL, NULL);
		hContact != NULL;
		hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, NULL))
	{
		if(isHandle(hContact)) {
			_hContact = hContact;
			_isNewContact = FALSE;
			return hContact;
		}
	}
	_isNewContact = TRUE;
	_hContact = INVALID_HANDLE_VALUE;
	return INVALID_HANDLE_VALUE;
}


} // namespace NExImport